using System;
using System.Collections.Generic;
using System.Net;
using System.IO;
using System.Text;
using System.Web.Services.Protocols;
using PSLibrary = Microsoft.Office.Project.Server.Library;

namespace MicrosoftFrance.MCS.EPM2007
{
    class PSITimeSheet
    {
        public static Guid CreateTimeSheetFor(Guid resourceGuid, Guid mgrUid, Guid periodUid, int index)
        {
            if (resourceGuid != Guid.Empty && mgrUid != Guid.Empty && periodUid != Guid.Empty)
            {
                TimeSheetWS.TimesheetDataSet timesheetDs = new TimeSheetWS.TimesheetDataSet();
                TimeSheetWS.TimesheetDataSet.HeadersRow headersRow = timesheetDs.Headers.NewHeadersRow();
                headersRow.RES_UID = resourceGuid;
                headersRow.TS_UID = Guid.NewGuid();
                headersRow.WPRD_UID = periodUid;
                headersRow.TS_CREATOR_RES_UID = mgrUid;
                headersRow.TS_NAME = "TS - " + PSIAdmin.GetTSPeriodName(periodUid) + " - " + index.ToString();
                headersRow.TS_COMMENTS = "Timesheet creatign using PSI";
                headersRow.TS_ENTRY_MODE_ENUM = (byte)PSLibrary.TimesheetEnum.EntryMode.Weekly;
                
                timesheetDs.Headers.AddHeadersRow(headersRow);

                // Create the timesheet with the default line types specified by the admin
                Program.timeSheet.CreateTimesheet(timesheetDs, TimeSheetWS.PreloadType.Default);
                return headersRow.TS_UID;
            }
            else
                return Guid.Empty;
        }

        public static TimeSheetWS.TimesheetDataSet ReadTimesheetByPeriod(Guid resUID, Guid periodUID)
        {
            return Program.timeSheet.ReadTimesheetByPeriod(resUID, periodUID, MicrosoftFrance.MCS.EPM2007.TimeSheetWS.Navigation.Current);
        }

        public static TimeSheetWS.TimesheetDataSet AddActualsRows(TimeSheetWS.TimesheetDataSet tsDS, AdminWS.TimePeriodDataSet timePeriodDs, List<Guid> adminLines, List<Guid> tsLines, Guid periodUid, AppLog appLog, int taskRemaingHoursBag)
        {
            decimal totalActual = tsDS.Headers[0].TS_GRAND_TOTAL_ACT_VALUE;
            decimal taskTotalToSubmit = 0;
            decimal taskTotalToSubmitAux = 0;
            decimal totalToSubmit = 1920000 - taskRemaingHoursBag;   //24h -> 1440000; 32h -> 1920000; 40h -> 2400000

            // taskRemaingHoursBag 8h -> 480000 are kept to split them randomly over all the tasks
            // Get the number of 30min slots
            int halfhourincrements = taskRemaingHoursBag / 30000;

            int taskHourBag = 0;
            int randomTime = 0;

            Random randObj1 = new Random(DateTime.Now.Millisecond);
           
            for (int i = 0; i < tsDS.Actuals.Count; i++)
            {
                try
                {

                    if (tsDS.Lines[i].TASK_UID != null && tsDS.Lines[i].TASK_UID != Guid.Empty)
                    {
                        if (i == tsDS.Actuals.Count)
                        {
                            taskTotalToSubmit = totalToSubmit;
                        }
                        else
                        {
                            taskTotalToSubmit = tsDS.Actuals.Count > 0 ? Math.Round(totalToSubmit / (tsDS.Actuals.Count - i), 0) : totalToSubmit;
                            // need to do random allocation in 30 min chuncks: 30000
                            if (halfhourincrements > 0)
                                randomTime = randObj1.Next(1, halfhourincrements);
                            else
                                randomTime = 0;

                            taskHourBag = randomTime * 30000;
                            taskTotalToSubmitAux = Math.Round(taskTotalToSubmit + taskHourBag);
                            totalToSubmit = totalToSubmit - taskTotalToSubmit;
                            halfhourincrements = halfhourincrements - randomTime;
                        }
                        // if last task force remaining hours
                        if (i == tsDS.Actuals.Count - 1)
                        {
                            taskTotalToSubmitAux = taskTotalToSubmitAux + halfhourincrements * 30000;
                        }

                        tsDS.Actuals[i].TS_ACT_PLAN_VALUE = taskTotalToSubmitAux;
                        tsDS.Actuals[i].TS_ACT_VALUE = taskTotalToSubmitAux;
                        tsDS.Actuals[i].TS_ACT_COMMENT = "Updated existing line via PSI utility";

                        appLog.WriteLine(string.Format("Update Actual- SD:{0} FD:{1} AW:{2}", tsDS.Actuals[i].TS_ACT_START_DATE, tsDS.Actuals[i].TS_ACT_FINISH_DATE, tsDS.Actuals[i].TS_ACT_VALUE), false);
                    }
                }
                catch (Exception ex)
                {
                    appLog.WriteLine(string.Format("Exception in AddActualsRow s: {0}", ex.Message));
                    continue; // Because there are errors sometimes ..
                }

                appLog.WriteLine(string.Format("totalToSubmit: {0}; Actuals Count: {1}; HH Incr.: {2}", totalToSubmit, tsDS.Actuals.Count, halfhourincrements), false);

            }

            // Randomly add Admin Time (-> 8h per week)
            Random randObj = new Random(DateTime.Now.Millisecond);
            int index = randObj.Next(adminLines.Count);
            TimeSheetWS.TimesheetDataSet.ActualsRow actualsRow = tsDS.Actuals.NewActualsRow();
            actualsRow.TS_ACT_COMMENT = "Updated with default values by code sample";
            // If no assignment add 40h of administrative time else 8h
            actualsRow.TS_ACT_PLAN_VALUE = tsDS.Actuals.Count == 0 ? 2400000 : 480000; 
            actualsRow.TS_ACT_VALUE = actualsRow.TS_ACT_PLAN_VALUE;
            actualsRow.TS_ACT_START_DATE = GetTSPeriodDate(timePeriodDs, periodUid, true);
            actualsRow.TS_ACT_FINISH_DATE = GetTSPeriodDate(timePeriodDs, periodUid, false);
            actualsRow.TS_LINE_UID = adminLines[index];
            tsDS.Actuals.AddActualsRow(actualsRow);     

            return tsDS;
        }

        public static Guid FindFirstEmptyPeriod(Guid resUid, AdminWS.TimePeriodDataSet timePeriodDs)
        {
            TimeSheetWS.TimesheetDataSet timeSheetDs;

            for (int i = 0; i < timePeriodDs.TimePeriods.Count; i++)
            {
                timeSheetDs = Program.timeSheet.ReadTimesheetByPeriod(resUid, timePeriodDs.TimePeriods[i].WPRD_UID, TimeSheetWS.Navigation.Current);
                if (timeSheetDs.Headers.Count == 0)
                {
                    return timePeriodDs.TimePeriods[i].WPRD_UID;
                }
            }
            return Guid.Empty;
        }

        public static Guid FindFirstEmptyPeriod(Guid resUid, AdminWS.TimePeriodDataSet timePeriodDs, DateTime startDate)
        {
            TimeSheetWS.TimesheetDataSet timeSheetDs;

            for (int i = 0; i < timePeriodDs.TimePeriods.Count; i++)
            {
                timeSheetDs = Program.timeSheet.ReadTimesheetByPeriod(resUid, timePeriodDs.TimePeriods[i].WPRD_UID, TimeSheetWS.Navigation.Current);
                if (timePeriodDs.TimePeriods[i].WPRD_START_DATE >= startDate && timeSheetDs.Headers.Count == 0)
                {
                    return timePeriodDs.TimePeriods[i].WPRD_UID;
                }
            }
            return Guid.Empty;
        }

        public static DateTime GetTSPeriodDate(AdminWS.TimePeriodDataSet timePeriodDs, Guid timeSheetPeriodUID, bool startDate)
        {
            for (int i = 0; i < timePeriodDs.TimePeriods.Count; i++)
            {
                if (timePeriodDs.TimePeriods[i].WPRD_UID == timeSheetPeriodUID)
                {
                    if (startDate)
                        return timePeriodDs.TimePeriods[i].WPRD_START_DATE;
                    else
                        return timePeriodDs.TimePeriods[i].WPRD_FINISH_DATE;
                }
            }
            return DateTime.Now;
        }

        //public static void ImpersonateTSUID(Guid resUID)
        //{

        //    Guid siteID = new Guid("82A561AC-C0E5-42B7-ADC1-BA28B699271E");
        //    ResourceDerived resourceDerived = new ResourceDerived();

        //    resourceDerived.Url = "https://ict.epm.com/psref/_vti_bin/psi/resource.asmx";
        //    resourceDerived.Credentials = CredentialCache.DefaultCredentials;
        //    string userAcct = "AspNetSqlMembershipProvider:Allen Tony";
        //    bool isWindowsUser = true;

        //    resUID = PSIResource.GetResourceUidFromNtAccount(userAcct, out isWindowsUser);
        //    ResourceDerived.SetImpersonationContext(isWindowsUser, userAcct, resUID, Guid.NewGuid(), siteID, "1033");

        //    Guid impersonatedUserGuid = resourceDerived.GetCurrentUserUid();

        //    TimesheetDerived timesheetDerived = new TimesheetDerived();
        //    timesheetDerived.Url = "https://ict.epm.com/psref/_vti_bin/psi/timesheet.asmx";
        //    timesheetDerived.Credentials = CredentialCache.DefaultCredentials;

        //    TimesheetDerived.SetImpersonationContext(isWindowsUser, userAcct, resUID, Guid.NewGuid(), siteID, "1033");

        //    Guid jobUid = Guid.NewGuid();
        //    timesheetDerived.QueueRecallTimesheet(jobUid, new Guid("016530E5-5212-493D-84D1-8AF7EF9CDA01"));
        //    timesheetDerived.QueueDeleteTimesheet(Guid.NewGuid(), new Guid("016530E5-5212-493D-84D1-8AF7EF9CDA01"));
        //}

        private static TimesheetDerived ImpersonateTSByResUID(bool isWindowsUser, Guid resUID, string userAcct, Guid siteID, string lcID, string url)
        {
            TimesheetDerived timesheetDerived = new TimesheetDerived();
            timesheetDerived.Url = url;
            timesheetDerived.Credentials = CredentialCache.DefaultCredentials;
            TimesheetDerived.SetImpersonationContext(isWindowsUser, userAcct, resUID, Guid.NewGuid(), siteID, lcID);
            return timesheetDerived;
        }

        public static int DeleteAllTSByResUID(Guid resUID, DateTime startDate, DateTime finishDate, StreamWriter sw, TimesheetDerived tsDerived)
        {
            // PSLibrary.TimesheetEnum.ListSelect.AllExisting = 31: InProgress | Submitted | Acceptable | Approved | Rejected.
            TimeSheetWS.TimesheetListDataSet tsLDS = Program.timeSheet.ReadTimesheetList(resUID, startDate, finishDate, 31);

            ExceptionHandlers exH = new ExceptionHandlers(sw);
            int nbTSDeleted = 0;

            for (int i = 0; i < tsLDS.Timesheets.Count; i++)
            {
                bool noError = true;
                Guid tsUID = tsLDS.Timesheets[i].TS_UID;
                try
                {
                    Guid jobUid = Guid.NewGuid();

                    if (tsDerived != null)
                        tsDerived.QueueDeleteTimesheet(jobUid, tsUID);
                    else
                        Program.timeSheet.QueueDeleteTimesheet(jobUid, tsUID);

                    //// Wait for last delete job to complete
                    if(i == tsLDS.Timesheets.Count - 1)
                        PSIQueueSystem.WaitForQueue(Program.queueSystem, jobUid);

                    nbTSDeleted++;
                }
                catch (SoapException ex)                
                {
                    exH.HandleSoapException(ex);
                    noError = false;
                }
                catch (WebException ex)
                {
                    exH.HandleWebException(ex);
                    noError = false;
                }
                catch (Exception ex)
                {
                    exH.HandleException(ex);
                    noError = false;
                }
                finally
                {
                    // Write log
                    if (noError)
                        AppLog.WriteLine(sw, string.Format("Succesfully deleted TS: {0} - {1}, for Resource: {2}", i, tsUID, resUID)); 
                    else
                        AppLog.WriteLine(sw, string.Format("Failed to deleted TS: {0} - {1}, for Resource: {2}", i, tsUID, resUID)); 
                }
            }
            return nbTSDeleted;
        }

        public static int DeleteAllTSForAllRes(DateTime startDate, DateTime finishDate, StreamWriter sw, string connectionString, Guid siteID, string tsWSUrl)
        {
            // Retrieve a list of all resources
            ResourceWS.ResourceDataSet resDS = Program.resource.ReadUserList(MicrosoftFrance.MCS.EPM2007.ResourceWS.ResourceActiveFilter.Active);
            TimesheetDerived timesheetDerived = null;
            int nbTSDeleted = 0;
            for(int i = 0; i < resDS.Resources.Count; i++)
            {
                if (siteID != Guid.Empty && tsWSUrl != string.Empty)
                {
                    timesheetDerived = ImpersonateTSByResUID(resDS.Resources[i].RES_IS_WINDOWS_USER, resDS.Resources[i].RES_UID, resDS.Resources[i].WRES_ACCOUNT, siteID, "1033", tsWSUrl);
                }
                // Force status update in Published DB so we can delete all timesheet !!!!!!
                string updateStatus = "UPDATE MSP_TIMESHEETS SET TS_STATUS_ENUM = 0 WHERE RES_UID='" + resDS.Resources[i].RES_UID.ToString() + "'";

                if(GeneralHelper.ConnectAndExecuteNonQuery(connectionString, updateStatus, sw) != -1)
                   nbTSDeleted = nbTSDeleted + DeleteAllTSByResUID(resDS.Resources[i].RES_UID, startDate, finishDate, sw, timesheetDerived);
            }

            return nbTSDeleted;
        }

        public static int DeleteAllTSForOneRes(Guid resUID, DateTime startDate, DateTime finishDate, StreamWriter sw, string connectionString)
        {
            int nbTSDeleted = 0;

            // Force status update in Published DB so we can delete all timesheet !!!!!!
            string updateStatus = "UPDATE MSP_TIMESHEETS SET TS_STATUS_ENUM = 0 WHERE RES_UID='" + resUID.ToString() + "'";

            if (GeneralHelper.ConnectAndExecuteNonQuery(connectionString, updateStatus, sw) != -1)
                nbTSDeleted = nbTSDeleted + DeleteAllTSByResUID(resUID, startDate, finishDate, sw, null);

            return nbTSDeleted;
        }

        public static void ApproveAllTS(Guid resUID, DateTime startDate, DateTime finishDate)
        {
            Guid[] resUIDs = { resUID };
            TimeSheetWS.TimesheetListDataSet tslDS = Program.timeSheet.ReadTimesheetsPendingApproval(startDate, finishDate, resUIDs);

            Guid jobUid;

            for(int i = 0; i < tslDS.Timesheets.Count; i++)
            {
                jobUid = Guid.NewGuid();
                Program.timeSheet.QueueReviewTimesheet(jobUid, tslDS.Timesheets[i].TS_UID, Guid.Empty, "Approved via PSI", MicrosoftFrance.MCS.EPM2007.TimeSheetWS.Action.Approve);
            }
        }
    }

    class TimesheetDerived : TimeSheetWS.TimeSheet
    {
        private static String ContextString = String.Empty;

        protected override WebRequest GetWebRequest(Uri uri)
        {
            //here we are overriding the GetWebRequest method and adding the 2 web request headers
            WebRequest webRequest = base.GetWebRequest(uri);
            if (ContextString != String.Empty)
            {
                webRequest.UseDefaultCredentials = true;

                bool isImpersonating = (System.Security.Principal.WindowsIdentity.GetCurrent(true) != null);
                webRequest.Credentials = CredentialCache.DefaultNetworkCredentials;

                webRequest.Headers.Add("PjAuth", ContextString);
                webRequest.Headers.Add("ForwardFrom", "/_vti_bin/psi/pwa.asmx");

                webRequest.PreAuthenticate = true;
            }
            return webRequest;
        }

        public static void SetImpersonationContext(bool isWindowsUser, String userNTAccount, Guid userGuid, Guid trackingGuid, Guid siteId, String lcid)
        {
            ContextString = GetImpersonationContext(isWindowsUser, userNTAccount, userGuid, trackingGuid, siteId, lcid);
        }

        private static String GetImpersonationContext(bool isWindowsUser, String userNTAccount, Guid userGuid, Guid trackingGuid, Guid siteId, String lcid)
        {
            PSLibrary.PSContextInfo contextInfo = new PSLibrary.PSContextInfo(isWindowsUser, userNTAccount, userGuid, trackingGuid, siteId, lcid);
            String contextString = PSLibrary.PSContextInfo.SerializeToString(contextInfo);
            return contextString;
        }
    }
}
